#include "common.h"
#include "document.h"
#include "filerw.h"
#include "buffer.h"

Document::Document(): max_line(LINES * 5),
					  max_column(COLS),
					  pos_line(0),
					  pos_column(0),
					  offset_line(0),
					  offset_column(0),
					  columns(new int[max_line]),
					  total_line(1),
					  clipbord()
{
	memset(columns, 0, max_line);
	memset(fileName, 0, FILENAME_LEN);
	this->bufferWindow = newpad(this->max_line, this->max_column);
}

Document::~Document()
{
	if (this->bufferWindow)
		delwin(this->bufferWindow);
}

bool Document::moveTo(int new_line, int new_column)
{
	 if (new_line >= 0 && new_line < this->max_line &&			// 检查行号的合法性
		 new_column >= 0 && new_column <= this->max_column)		// 检查列号的合法性
	 {
		 pos_line = new_line;
		 
		 // 检查光标是否处于当前所显示的屏幕中
		 if (new_line >= offset_line && new_line < offset_line + LINES - 1)
		 {

		 }
		 // 否则更改逻辑屏幕的偏移量
		 else
		 {
			 // 超出了屏幕上边界
			 if (new_line < offset_line)
			 {
				 offset_line = new_line;
			 }
			 // 否则超出了屏幕下边界
			 else
			 {
				 offset_line = new_line - LINES + 1 + 1;
			 }
		 }
		 
		 if (new_column < max_column)
		 {
			 if (new_column <= columns[new_line])
			 {
				 this->pos_column = new_column;
				 wmove(bufferWindow, this->pos_line, this->pos_column);
			 }
			 else
			 {
				 if (new_column > columns[new_line])
				 {
					 this->moveTo(new_line, columns[new_line]);
				 }
				 else
				 {
					 beep();
				 }
			 }
			 return true;
		 }
		 else
		 {
			 return this->moveTo(new_line + 1, 0);			//  递规调用，将光标移到下一行的行首
		 }
	}
	else
	{
		beep();
	}
	return false;											// 设置出错
}

bool Document::moveOffset(int line, int column)
{
	return this->moveTo(this->pos_line + line, this->pos_column + column);
}

bool Document::feedLine()
{
	char *buffer = 0;
	if (this->pos_column < columns[this->pos_line])
	{
		buffer = new char[columns[this->pos_line] - this->pos_column + 1];
		winnstr(this->bufferWindow, buffer, columns[this->pos_line] - this->pos_column);
		for (int i = columns[this->pos_line]; i >= this->pos_column; i--)
		{
			wmove(this->bufferWindow, this->pos_line, i);
			wdelch(this->bufferWindow);
		}
	}
	
	/// Problem here, don't consider the last line
	for (int i = this->max_line; i > this->pos_line + 1; i--)
	{
		columns[i] = columns[i - 1];
	}
	columns[this->pos_line] = this->pos_column;
	
	this->moveTo(this->pos_line + 1, 0);
	//winsertln(this->bufferWindow);
	
	winsertln(this->bufferWindow);
	if (buffer)
	{
		winsstr(this->bufferWindow, buffer);
	}
	columns[this->pos_line] = buffer ? strlen(buffer) : 0;
	
	this->total_line++;
	return true;
}

bool Document::backspace()
{
	const char *buffer = 0;
	
	if (this->pos_column == 0)
	{
		if (this->pos_line != 0)
		{
			buffer = this->deleteLine(true);
			this->moveTo(this->pos_line - 1, columns[this->pos_line - 1]);
			winsstr(this->bufferWindow, buffer);
			
			// The cursor has moved to the line above, and set the new limition of the cursor
			columns[this->pos_line] += (buffer ? strlen(buffer) : 0);
		}
		else
		{
			beep();
		}
	}
	else
	{
		this->del();
	}
	return true;
}

bool Document::keydelete()
{
	if (this->pos_column < columns[this->pos_line])
	{
		this->moveTo(this->pos_line, this->pos_column + 1);
		this->del();
	}
	else
	{
		this->moveTo(this->pos_line + 1, 0);
		this->backspace();
	}
	return true;
}

bool Document::keyhome()
{
	return this->moveTo(this->pos_line, 0);
}

bool Document::keyend()
{
	return this->moveTo(this->pos_line, columns[this->pos_line]);
}

bool Document::keypageup()
{
	int offset = this->pos_line - this->offset_line;
	if (this->moveTo(this->pos_line - LINES, this->pos_column))
	{
		this->offset_line -= offset;
		return true;
	}
	else if (this->moveTo(0, this->pos_column))
	{
		this->offset_line = 0;
		return true;
	}
	return false;
}

bool Document::keypagedown()
{
	int offset = LINES - (this->pos_line - this->offset_line);
	if (this->moveTo(this->pos_line + LINES, this->pos_column))
	{
		this->offset_line += (offset - 1);
		return true;
	}
	return false;
}

bool Document::keytab()
{
	int spaces = 8 - (this->pos_column % 8);
	for (; spaces > 0; spaces--)
		this->add(' ');
	return true;
}

bool Document::add(char ch)
{
	winsch(this->bufferWindow, ch);
	columns[this->pos_line]++;
	this->moveTo(this->pos_line, this->pos_column + 1);
	return true;
}

bool Document::add(const char *str)
{
	int i = 0;
	while(str[i])
	{
		if (str[i] != '\n')
			this->add(str[i]);
		else
			this->feedLine();	
		i++;
	}
	return true;
}

bool Document::moveAdd(int line, int column, char ch)
{
	if (this->moveTo(line, column))
	{
		return this->add(ch);
	}

	return false;
}

bool Document::moveAdd(int line, int column, char *str)
{
	if (this->moveTo(line, column))
	{
		return this->add(str);
	}
	return false;
}

bool Document::loadFile(const char *fileName)
{
	if (fileName)
	{
		strncpy(this->fileName, fileName, FILENAME_LEN);
	}
	FileReader reader(this->fileName);
	char temp[1025];
	if (!reader.asyncRead(temp, 0, 1024, NULL))
		return false;
	wclear(this->bufferWindow);
	
	this->moveTo(0, 0);
	this->offset_column = this->offset_line = 0;
	this->total_line = 1;
	memset(columns, 0, max_line);
	
	this->add(temp);
	return true;
}

bool Document::saveFile(const char *fileName)
{
	if (fileName)
	{
		strncpy(this->fileName, fileName, FILENAME_LEN);
	}
	Buffer b(this->bufferWindow, this->total_line, this->columns);
	FileWriter f(this->fileName);
	if (!f.asyncWrite(b.getContent(), 0, b.getSize(), NULL))
		return false;
	else
		return true;
}

bool Document::del()
{
	if (this->moveTo(this->pos_line, this->pos_column - 1))
	{
		wdelch(this->bufferWindow);
		columns[this->pos_line]--;
		columns[this->pos_line] = columns[this->pos_line] < 0 ? 0 : columns[this->pos_line];
	}
	return true;
}

bool Document::del(int n)
{
	while(n > 0)
	{
		if (!this->del())
			break;
		n--;
	}
	return true;
}

const char *Document::findWord(bool del)
{
	int pos = this->pos_column; int len = 0;
	char *buffer = new char[this->columns[this->pos_line]];
	char *word = 0;
	
	mvwinnstr(this->bufferWindow, this->pos_line, 0, buffer, this->columns[this->pos_line]);
	wmove(this->bufferWindow, this->pos_line, this->pos_column);
	
	len = strlen(buffer);
	
	if (buffer[pos] !=' ')
	{
		int head = pos - 1, end = pos + 1;
		while (buffer[head] != ' ' && head >= 0)
			head--;
		while (buffer[end] != ' ' && end < len)
			end++;
		word = new char[end - head];
		strncpy(word, buffer + head + 1, end - head - 1);
		
		if (del)
		{
			this->moveTo(this->pos_line, end);
			this->del(end - head - 1);
		}
	}
	return word;
}

const char *Document::deleteLine(bool del)
{
	char *buffer = new char[this->columns[this->pos_line]];
	mvwinnstr(this->bufferWindow, this->pos_line, 0, buffer, this->columns[this->pos_line]);
	wmove(this->bufferWindow, this->pos_line, this->pos_column);
	
	// Delete the current line
	if (del)
	{
		wdeleteln(this->bufferWindow);
		if (this->pos_line < this->total_line)
			this->total_line--;
		// Change the last limitions of the cursor
		for (int i = this->pos_line; i < this->max_line; i++)
		{
			this->columns[i] = this->columns[i + 1];
		}
	}
	
	return buffer;
}

bool Document::cut()
{
	const char *word = this->findWord(true);
	
	if (word)
	{
		this->clipbord.set(word);
	}
	return true;
}

bool Document::cutLine()
{
	const char *line = this->deleteLine(true);
	if (line)
	{
		this->clipbord.set(line);
	}
	this->moveTo(this->pos_line - 1, columns[this->pos_line - 1]);
	return true;
}

bool Document::copyLine()
{
	const char *line = this->deleteLine(false);
	if (line)
	{
		this->clipbord.set(line);
	}
	return true;
}
bool Document::copy()
{
	const char *word = this->findWord(false);
	
	if (word)
	{
		this->clipbord.set(word);
	}
	return true;
}

bool Document::past()
{
	return this->add(this->clipbord.get());
}

bool Document::find(const char *keyword)
{
	char *line = 0; //= this->deleteLine(false);
	char *buffer = 0;
	int start = 0, end = 0;
	int len = 0;
	int o_line = this->pos_line;
	int o_column = this->pos_column;
	
	int i = 0;
	for (; i < this->total_line; i++)
	{
		start = end = 0;
		this->moveTo(i, 0);
		line = new char[this->columns[i]];
		winnstr(this->bufferWindow, line, this->columns[i]);
		len = strlen(line);
		while (start < len && end < len)
		{
			while(line[start] == ' ' && start < len)
				start++;
			end = start + 1;
			
			while(line[end] != ' ' && end < len)
				end++;
			
			buffer = new char[end - start + 1];
			buffer[end - start] = '\0';
			strncpy(buffer, line + start, end - start);
			if (strcmp(keyword, buffer) == 0)
			{
				this->moveTo(this->pos_line, end);
				this->del(end - start);
				wattron(this->bufferWindow, A_REVERSE);
				this->add(keyword);
				wattroff(this->bufferWindow, A_REVERSE);
			}
			start = end;
		}
	}
	this->moveTo(o_line, o_column);
	return true;
}

bool Document::replace(const char *keyword, const char *replaceword)
{
	char *line = 0; //= this->deleteLine(false);
	char *buffer = 0;
	int start = 0, end = 0;
	int len = 0;
	int o_line = this->pos_line;
	int o_column = this->pos_column;
	
	int i = 0;
	int count = 0;
	int diflen = strlen(keyword) - strlen(replaceword);
	
	for (; i < this->total_line; i++)
	{
		start = end = 0;
		count = 0;
		this->moveTo(i, 0);
		line = new char[this->columns[i]];
		winnstr(this->bufferWindow, line, this->columns[i]);
		len = strlen(line);
		while (start < len && end < len)
		{
			while(line[start] == ' ' && start < len)
				start++;
			end = start + 1;
			
			while(line[end] != ' ' && end < len)
				end++;
			
			buffer = new char[end - start + 1];
			buffer[end - start] = '\0';
			strncpy(buffer, line + start, end - start);
			if (strcmp(keyword, buffer) == 0)
			{
				this->moveTo(this->pos_line, end - count * diflen);
				this->del(end - start);
				this->add(replaceword);
				count++;
			}
			start = end;
		}
	}
	this->moveTo(o_line, o_column);
	return true;
	return true;
}

int Document::getPosLine()
{
	return this->pos_line;
}

int Document::getPosColumn()
{
	return this->pos_column;
}

int Document::getOffsetLine()
{
	return this->offset_line;
}

int Document::getOffsetColumn()
{
	return this->offset_column;
}

int Document::getMaxLine()
{
	return this->max_line;
}

int Document::getMaxColumn()
{
	return this->max_column;
}

WINDOW *Document::getBufferWindow()
{
	return this->bufferWindow;
}
